package com.jonlandrum.collections.BinarySearchTree.AVLTree;

import com.jonlandrum.collections.BinarySearchTree.LinkedBinarySearchNode;

public class LinkedAVLNode<T extends Comparable<T>> extends LinkedBinarySearchNode<T> implements AVLNode<T> {
    private int balanceFactor = 0;

    /**
     * Constructs an empty LinkedAVLNode.
     */
    public LinkedAVLNode() {}

    /**
     * Constructs a new LinkedAVLNode with the given data.
     *
     * @param data The data to store in this node
     */
    public LinkedAVLNode(T data) {
        super(data);
    }

    /**
     * Returns the balance factor of this node.
     *
     * @return The balance factor of this node
     */
    public int getBalanceFactor() {
        this.setBalanceFactor();
//        System.out.print(" | " + this.balanceFactor + " | ");
        return this.balanceFactor;
    }

    /**
     * Sets the balance factor of this node.
     */
    public void setBalanceFactor() {
        this.balanceFactor = (this.getRightHeight() - this.getLeftHeight());
    }

    /**
     * Returns the height of this node.
     *
     * @return The height of this node
     */
    public int getHeight() {
        return (Math.max(this.getLeft().getHeight(), this.getRight().getHeight()) + 1);
    }

    /**
     * Returns the height of the left subtree of this node.
     *
     * @return The height of the left subtree of this node
     */
    public int getLeftHeight() {
        return this.hasLeft() ? this.getLeft().getHeight() : 0;
    }

    /**
     * Returns the height of the right subtree of this node.
     *
     * @return The height of the right subtree of this node
     */
    public int getRightHeight() {
        return this.hasLeft() ? this.getLeft().getHeight() : 0;
    }

    /**
     * Returns the parent of this node.
     *
     * @return The parent of this node
     */
    @Override
    public AVLNode<T> getParent() {
        return (LinkedAVLNode<T>) super.getParent();
    }

    /**
     * Sets the parent of this node to null.
     */
    @Override
    public void setParent() {
        super.setParent();
        this.setBalanceFactor();
    }

    /**
     * Sets the parent of this node to the node parameter.
     *
     * @param node The node to set as this node's parent
     */
    @Override
    public void setParent(AVLNode<T> node) {
        super.setParent(node);
        this.setBalanceFactor();
    }

    /**
     * Returns the left child of this node.
     *
     * @return The left child of this node
     */
    @Override
    public AVLNode<T> getLeft() {
        return (LinkedAVLNode<T>) super.getLeft();
    }

    /**
     * Sets the left child of this node to null.
     */
    @Override
    public void setLeft() {
        super.setLeft();
        this.setBalanceFactor();
    }

    /**
     * Sets the left child of this node to the node parameter.
     *
     * @param node The node to set as this node's left child
     */
    @Override
    public void setLeft(AVLNode<T> node) {
        super.setLeft(node);
        this.setBalanceFactor();
    }

    /**
     * Returns the right child of this node.
     *
     * @return The right child of this node
     */
    @Override
    public AVLNode<T> getRight() {
        return (LinkedAVLNode<T>) super.getRight();
    }

    /**
     * Sets the right child of this node to null.
     */
    @Override
    public void setRight() {
        super.setRight();
        this.setBalanceFactor();
    }

    /**
     * Sets the right child of this node to the node parameter.
     *
     * @param node The node to set as this node's right child
     */
    @Override
    public void setRight(AVLNode<T> node) {
        super.setRight(node);
        this.setBalanceFactor();
    }

    /**
     * Returns the node that replaces this node.
     *
     * <p>This method first attempts to locate the in-order successor of this node.
     * The in-order successor of a given node is the next element in the collection in sorted order.
     * If an in-order successor doesn't exist, this method returns the in-order predecessor.
     * If neither exists, this method returns an empty node.</p>
     *
     * @return The node that replaces this node
     */
    @Override
    public AVLNode<T> getReplacement() {
        LinkedAVLNode<T> r = this;
        if (r.hasRight()) {
            r = (LinkedAVLNode<T>) r.getRight();
            while (r.hasLeft()) {
                r = (LinkedAVLNode<T>) r.getLeft();
            }
        } else if (r.hasLeft()) {
            r = (LinkedAVLNode<T>) r.getLeft();
            while (r.hasRight()) {
                r = (LinkedAVLNode<T>) r.getRight();
            }
        } else {
            r = new LinkedAVLNode<>();
        }
        return r;
    }
}